feat(keycardai-oauth): add server subpackage with framework-free primitives#95
Conversation
…itives Extract protocol-agnostic server components from keycardai-mcp into keycardai.oauth.server per the Protocol-Agnostic SDK KEP (Tier 1). New keycardai.oauth.server modules: - access_context: AccessContext for non-throwing token access - credentials: ApplicationCredential, ClientSecret, WebIdentity, EKSWorkloadIdentity - verifier: TokenVerifier with local AccessToken model (no MCP dependency) - exceptions: OAuthServerError base + all framework-free exceptions - _cache: JWKSCache/JWKSKey for JWKS key caching - client_factory: ClientFactory protocol + DefaultClientFactory - private_key: PrivateKeyManager, FilePrivateKeyStorage keycardai-mcp changes: - Server auth modules now re-export from keycardai.oauth.server - MCPServerError is an alias for OAuthServerError - MissingContextError stays MCP-specific (references FastMCP Context) - All existing imports continue to work (no breaking changes) - Tests updated to patch canonical module paths
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-mcp",
"package_dir": "packages/mcp",
"has_changes": true,
"current_version": "0.21.0",
"next_version": "0.22.0",
"increment": "MINOR"
},
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.9.0",
"next_version": "0.10.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
- Add token_exchange module with exchange_tokens_for_resources() orchestration (KEP Tier 1 gap) - Rename WebIdentity param mcp_server_name -> server_name with backward-compatible alias; default storage dir ./mcp_keys -> ./server_keys - Add mcp_server_url/missing_mcp_server_url backward-compat aliases to AuthProviderConfigurationError (prevents breaking fastmcp callers) - Fix _get_kid_and_algorithm returning list instead of tuple
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-mcp",
"package_dir": "packages/mcp",
"has_changes": true,
"current_version": "0.21.0",
"next_version": "0.22.0",
"increment": "MINOR"
},
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.9.0",
"next_version": "0.10.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
Dropped inline "# re-exported from keycardai.oauth.server" comments on __all__ entries, narrative comments above import blocks, and ASCII divider headers — all redundant with the imports they describe.
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-mcp",
"package_dir": "packages/mcp",
"has_changes": true,
"current_version": "0.21.0",
"next_version": "0.22.0",
"increment": "MINOR"
},
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.9.0",
"next_version": "0.10.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
cmars
left a comment
There was a problem hiding this comment.
LGTM
A few questions & forward-thinking comments but nothing blocking. Thanks for this!
| client_factory=self.client_factory, | ||
| ) | ||
|
|
||
| def grant(self, resources: str | list[str], user_identifier: Callable[..., str] | None = None): |
There was a problem hiding this comment.
Probably getting well into follow-up territory... could we also extract these kind of decorators to keycardai.oauth.server?
There was a problem hiding this comment.
Leaving for follow-up. The accepted KEP lists @grant() as genuinely MCP-specific because it introspects mcp.server.fastmcp.Context / mcp.shared.context.RequestContext. A protocol-agnostic @protect() is in PR #97 (keycardai-starlette).
Address PR #95 review comments from cmars: 1. Revert WebIdentity default storage_dir to "./mcp_keys" and key_id prefix to "mcp-server-". Changing these would silently break existing keycardai-mcp services on upgrade: they would look for keys in a new empty directory and regenerate identity, losing their registered client identity with Keycard. 2. Move oauth-server-specific tests (test_verifier, test_cache, test_application_identity -> test_credentials) from packages/mcp/tests to packages/oauth/tests/keycardai/oauth/server/ so coverage lives with the canonical oauth.server modules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-mcp",
"package_dir": "packages/mcp",
"has_changes": true,
"current_version": "0.21.0",
"next_version": "0.22.0",
"increment": "MINOR"
},
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.9.0",
"next_version": "0.10.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
…tion warning Switch WebIdentity default storage_dir back to ./server_keys (aligning with the protocol-agnostic naming from this PR), but transparently fall back to ./mcp_keys when no storage_dir is passed, ./server_keys does not exist, and ./mcp_keys does. The fallback emits a DeprecationWarning pointing at the explicit configuration or migration paths. This preserves zero-config upgrades for existing keycardai-mcp services (they keep finding their existing keys) while giving new installs the new default. The fallback will be removed in a future release. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📦 Release PreviewThis analysis shows the expected release impact: 📈 Expected Version Changes📋 Package Details[
{
"package_name": "keycardai-mcp",
"package_dir": "packages/mcp",
"has_changes": true,
"current_version": "0.21.0",
"next_version": "0.22.0",
"increment": "MINOR"
},
{
"package_name": "keycardai-oauth",
"package_dir": "packages/oauth",
"has_changes": true,
"current_version": "0.9.0",
"next_version": "0.10.0",
"increment": "MINOR"
}
]📝 Changelog PreviewThis comment was automatically generated by the release preview workflow. |
Summary
keycardai-mcpinto a newkeycardai.oauth.serversubpackage per the Protocol-Agnostic SDK KEP (Tier 1)mcp,aiohttp,aiosqlite, Starlette)keycardai-mcpimports continue to work via re-exportsNew
keycardai.oauth.servermodulesexceptionsOAuthServerErrorbase + 20 framework-free exceptionsaccess_contextAccessContext— non-throwing token access with per-resource errorscredentialsApplicationCredential,ClientSecret,WebIdentity,EKSWorkloadIdentityverifierTokenVerifier+ localAccessTokenmodel (replaces MCP dep)_cacheJWKSCache/JWKSKeyfor JWKS key cachingclient_factoryClientFactoryprotocol +DefaultClientFactoryprivate_keyPrivateKeyManager,FilePrivateKeyStorage, storage protocolkeycardai-mcpchangesMCPServerError = OAuthServerErroralias preserves existing catch patternsMissingContextErrorstays MCP-specific (references FastMCPContext)provider.pykeeps MCP-specific@grant()decorator — imports from canonicaloauth.serverTest plan
keycardai-oauthtests pass (142/142)keycardai-mcptests pass (all, 16 interactive skipped)keycardai-mcp-fastmcptests pass (51/51)keycardai.mcp.server.auth.*still resolves)MCPServerError is OAuthServerErroridentity check passesAccessContextis the same class from both import paths🤖 Generated with Claude Code